﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.CSharp;

namespace FileArchive
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void txtExpr_TextChanged(object sender, EventArgs e)
        {
            var exprNode = Parse(txtExpr.Text);
            if (exprNode != null)
                txtResult.Text = exprNode.Compute().ToString();
            
        }

        private IEnumerable<string> LexicalParse(string text)
        {
            int pos = 0;
            int state = 0;
            StringBuilder tokenBuilder = new StringBuilder();
            while (pos < text.Length)
            {
                char curChar = text[pos];
                switch (state)
                {
                    case 0:
                        if (char.IsDigit(curChar))
                            state = 1;
                        else if (char.IsLetter(curChar))
                            state = 2;
                        else if (curChar == '+' || curChar == '-' || curChar == '*' 
                            || curChar == '/' || curChar == '%' || curChar=='(' || curChar == ')')
                            state = 3;
                        else
                            pos++;
                        break;
                    case 1:
                        if (char.IsDigit(curChar) || curChar == '.')
                        {
                            tokenBuilder.Append(curChar);
                            pos++;
                        }
                        else
                        {
                            yield return tokenBuilder.ToString();
                            tokenBuilder.Clear();
                            state = 0;
                        }
                        break;
                    case 2:
                        if (char.IsLetter(curChar) || curChar == '_')
                        {
                            tokenBuilder.Append(curChar);
                            pos++;
                        }
                        else
                        {
                            yield return tokenBuilder.ToString();
                            tokenBuilder.Clear();
                            state = 0;
                        }
                        break;
                    case 3:
                        yield return curChar.ToString();
                        state = 0;
                        pos++;
                        break;
                }
            }
            yield return tokenBuilder.ToString();
        }

        string ReadWord(string[] words, ref int pos)
        {
            if (pos < words.Length)
                return words[pos++];
            else
                throw new InvalidOperationException();
        }

        double ReadConst(string[] words, ref int pos)
        {
            return double.Parse(ReadWord(words, ref pos));
        }

        private ExpressionNode Parse(string text)
        {
            try
            {
                var words = LexicalParse(text).ToArray();
                int pos = 0;
                return ParseExpr(words, ref pos);
            }
            catch (Exception)
            {
                return null;
            }
        }

        private ExpressionNode ParseExpr(string[] words, ref int pos)
        {
            return ParseBinaryExpr(words, ref pos);
        }

        private ExpressionNode ParseBinaryExpr(string[] words, ref int pos)
        {
            ExpressionNode left;
            left = ParseBinaryExpr1(words, ref pos);
            do
            {
                if (pos >= words.Length)
                    return left;
                var op = words[pos];
                if (op == "+" || op == "-")
                {
                    pos++;
                    BinaryNode rs = new BinaryNode();
                    rs.Operator = op[0];
                    rs.Left = left;
                    rs.Right = ParseBinaryExpr1(words, ref pos);
                    left = rs;
                }
                else
                    return left;
            } while (true);
        }

        private ExpressionNode ParseBinaryExpr1(string[] words, ref int pos)
        {
            ExpressionNode left;
            left = ParseUnaryExpr(words, ref pos);
            while (true)
            {
                if (pos >= words.Length)
                    return left;
                var op = words[pos];
                if (op == "*" || op == "/" || op == "%")
                {
                    pos++;
                    BinaryNode rs = new BinaryNode();
                    rs.Operator = op[0];
                    rs.Left = left;
                    rs.Right = ParseUnaryExpr(words, ref pos);
                    left = rs;
                }
                else
                    return left;
            }
        }

        private ExpressionNode ParseUnaryExpr(string[] words, ref int pos)
        {
            if (words[pos] == "(")
            {
                pos++;
                var node = ParseExpr(words, ref pos);
                if (ReadWord(words, ref pos) != ")")
                    throw new InvalidOperationException();
                return node;
            }
            else if (char.IsLetter(words[pos][0]) || words[pos] == "-")
            {
                var node = new UnaryNode();
                node.Operation = ReadWord(words, ref pos);
                node.Child = ParseUnaryExpr(words, ref pos);
                return node;
            }
            else
            {
                var node = new ConstantNode();
                node.Value = ReadConst(words, ref pos);
                return node;
            }
        }

        private void txtResult_TextChanged(object sender, EventArgs e)
        {
            try
            {
                string fileName = @"archive.dll";
                Archive archive = Archive.FromFile(fileName, txtResult.Text);
                var form = new ArchiveForm();
                form.Archive = archive;
                this.Hide();
                form.ShowDialog();
                this.Close();
            }
            catch (Exception)
            { }
        }
    }

    public abstract class ExpressionNode
    {
        public abstract double Compute();
    }

    public class ConstantNode : ExpressionNode
    {
        public double Value;


        public override double Compute()
        {
            return Value;
        }
    }

    public class BinaryNode : ExpressionNode
    {
        public ExpressionNode Left, Right;
        public char Operator;
        public override double Compute()
        {
            switch (Operator)
            {
                case '+':
                    return Left.Compute() + Right.Compute();
                case '-':
                    return Left.Compute() - Right.Compute();
                case '*':
                    return Left.Compute() * Right.Compute();
                case '/':
                    return Left.Compute() / Right.Compute();
                case '%':
                    return Left.Compute() % Right.Compute();
            }
            return 0.0;
        }
    }

    public class UnaryNode : ExpressionNode
    {
        public ExpressionNode Child;
        public string Operation;
        public override double Compute()
        {
            switch (Operation)
            {
                case "-":
                    return -Child.Compute();
                case "sin":
                    return Math.Sin(Child.Compute());
                case "cos":
                    return Math.Cos(Child.Compute());
                case "tan":
                    return Math.Tan(Child.Compute());
                case "abs":
                    return Math.Abs(Child.Compute());
                case "sgn":
                    return Math.Sign(Child.Compute());
                case "log":
                    return Math.Log(Child.Compute());
                case "exp":
                    return Math.Exp(Child.Compute());
            }
            return 0.0;
        }
    }
}
